﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Diagnostics;

namespace SharpMap.Control
{
    public class Tracker
    {
        protected UserControl _userControl = null;

        protected System.Windows.Forms.MouseEventHandler mu = null;
        protected System.Windows.Forms.MouseEventHandler mm = null;
        protected System.Windows.Forms.MouseEventHandler md = null;

        protected Point _oldP1 = new Point(0, 0);
        protected Point _oldP2 = new Point(0, 0);

        protected Point _p1 = new Point(0, 0);
        protected Point _p2 = new Point(0, 0);
        protected bool _bDone = true;

        protected Graphics _pg1 = null;
        protected Image _image = null;
        
        protected TextureBrush _staticTextureBrush = null;
        protected Pen _staticPenRubout = null;

        private int _width = 1;
        private Color _color = Color.FromArgb(255, 0, 0, 0);

        [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
        private static extern bool BitBlt(
                                        IntPtr hdcDest, // handle to destination DC
                                        int nXDest,  // x-coord of destination upper-left corner
                                        int nYDest,  // y-coord of destination upper-left corner
                                        int nWidth,  // width of destination rectangle
                                        int nHeight, // height of destination rectangle
                                        IntPtr hdcSrc,  // handle to source DC
                                        int nXSrc,   // x-coordinate of source upper-left corner
                                        int nYSrc,   // y-coordinate of source upper-left corner
                                        System.Int32 dwRop  // raster operation code
                                        );

        public Tracker(UserControl uc)
        {
            this._userControl = uc;
            RegisterMouseEvent();
        }

        protected void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            Debug.WriteLine("TSRubberBandObject - CreateGraphics in MouseDown");

            Rectangle r = this._userControl.ClientRectangle;
            // Debug.WriteLine("1 pictureBox.ClientRectangle: " + r.ToString());

            // Translate the top-left point of the PictureBox from client coordinates of the
            // PictureBox to client coordinates of the parent of the PictureBox.
            Point tl = new Point(r.Left, r.Top);
            tl = this._userControl.PointToScreen(tl);
            tl = this._userControl.Parent.PointToClient(tl);
            // pictureBox.SetBounds(tl.X, tl.Y, r.Width, r.Height, BoundsSpecified.All);

            // Get a Graphics object for the PictureBox, and create a bitmap to contain the 
            // current contents of the PictureBox.
            Graphics g1 = this._userControl.CreateGraphics();
            this._image = new Bitmap(r.Width, r.Height, g1);

            //// Reset the Bounds of the PictureBox
            //// There seems to be a bug in the Bounds attribute in the PictureBox control.
            //// The top-left point must be set to the top-left client point translated to screen
            //// then back to the parents client space, and (here's the weird part) the width and
            //// height must be increased by the offset from the origin, or the image will be 
            //// clipped to the original PictureBox area.  VS Beta 2.
            //pictureBox.Bounds = new Rectangle(tl.X, tl.Y, r.Width + tl.X, r.Height + tl.Y);

            //// Now assign the new image area we just created.  This will be the screen area
            //// that we capture via BitBlt for our rubout pen.
            //pictureBox.Image = i;

            //// Reset the bounds once again, although this time we can do it as it seems you
            //// should.
            //pictureBox.SetBounds(tl.X, tl.Y, r.Width, r.Height, BoundsSpecified.All);

            // Get a Graphics object for the image
            Graphics g2 = Graphics.FromImage(this._image);

            // Now get handles to device contexts, and perform the bit blitting operation.
            IntPtr dc1 = g1.GetHdc();
            IntPtr dc2 = g2.GetHdc();
            BitBlt(dc2, 0, 0, r.Width, r.Height, dc1, 0, 0, 0x00CC0020 /* dest = source*/);

            // Clean up !!
            g1.ReleaseHdc(dc1);
            g2.ReleaseHdc(dc2);

            _p1 = new Point(e.X, e.Y);
            _bDone = false;
        }

        private void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (_bDone)
            {
                return;
            }

            if (_pg1 == null)
            {
                _pg1 = this._userControl.CreateGraphics();
            }

            if (this._userControl.DisplayRectangle.Contains(e.X, e.Y))
            {
                _p2 = new Point(e.X, e.Y);

                // We are going to be drawing a new line, so draw over the area we are
                // about to impact with the cached background image taken before the 
                // first line is drawn.
                if (_oldP1 == _oldP2)
                {
                    DrawXorLine(_pg1, _p1, _p2, false);
                }
                else
                {
                    // Debug.WriteLine("    Undrawing line");
                    DrawXorLine(_pg1, _oldP1, _oldP2, true);
                }
                // Debug.WriteLine("Drawing line");
                DrawXorLine(_pg1, _p1, _p2, false);
                _oldP1 = _p1;
                _oldP2 = _p2;
            }
        }

        protected void OnMoveUp(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            //p2 = new Point(e.X, e.Y);

            //oldP1 = oldP2;
            //bDone = true;

            //staticTextureBrush = null;
            //staticPenRubout = null;
        }

        private void DrawXorLine(Graphics g, Point p1, Point p2, Boolean bRubOut)
        {
            if (_bDone)
            {
                return;
            }

            g.CompositingMode = CompositingMode.SourceOver;

            if (bRubOut && _staticTextureBrush == null && _staticPenRubout == null)
            {
                if (this._image != null)
                {
                    _staticTextureBrush = new TextureBrush(this._image);
                    _staticPenRubout = new Pen(_staticTextureBrush, this._width + 2);
                    // Debug.WriteLine("Creating staticPenRubout");
                }
                else
                {
                    g.Dispose();
                    Debug.WriteLine("Cannot create staticPenRubout");
                }
            }

            if (bRubOut && _staticPenRubout != null && !(p1 == p2))
            {
                g.DrawLine(_staticPenRubout, p1, p2);
                // Debug.WriteLine("		Erase line");
            }
            else
            {
                Pen p = new Pen(this._color, this._width);
                //p.DashStyle = this.DashStyle;

                g.DrawLine(p, _p1, _p2);
                // Debug.WriteLine("Draw line");
            }
        }

        private void RegisterMouseEvent()
        {
            this.md = new System.Windows.Forms.MouseEventHandler(this.OnMouseDown);
            this.mu = new System.Windows.Forms.MouseEventHandler(this.OnMoveUp);
            this.mm = new System.Windows.Forms.MouseEventHandler(this.OnMouseMove);

            this._userControl.MouseUp += this.mu;
            this._userControl.MouseMove += this.mm;
            this._userControl.MouseDown += this.md;
        }

        private void UnRegisterMouseEvent()
        {
            this._userControl.MouseUp -= this.mu;
            this._userControl.MouseMove -= this.mm;
            this._userControl.MouseDown -= this.md;
            this.md = null;
            this.mu = null;
            this.mm = null;
        }

        public void Dispose()
        {
            UnRegisterMouseEvent();
        }

    }
}
